#include "listview.h"
#include <allegro5/allegro_primitives.h>
#include <boost/bind.hpp>
#include <cmath>
#include "font.h"
#include "app.h"
#include "scrollskin.h"
#include "util.h"
using namespace boost;
using namespace std;

ListView::ListView(Rect r, App *pApp) 
: View(r, pApp->skin()->font()), m_pApp(pApp), 
  m_rowLabel(0, 2, m_pFont, 0, 0, 0, m_pApp->skin()), 
  m_offset(0), m_curIndex(ListView::NoIndex), 
  m_curCol(ListView::NoCol),
  m_scrollBar(m_pApp->skin()->scrollSkin(), ScrollBar::Vertical),
  m_editing(false), m_editIndex(ListView::NoIndex) {
     m_views.push_back(&m_scrollBar);
     // connect to scrollbar onPosChange signal
     m_scrollConn = m_scrollBar.onPosChange(bind(&ListView::updateOffset, this, _1));
     // setup relative sizes of everything
     setRect(r);
}

int ListView::count() const {
     return m_items.size();
}

const std::string& ListView::itemAt(int i) const {
     assert(i<count());
     return m_items[i];
}

void ListView::addItem(const std::string &str) {
     unsigned int len = nameLength();
     m_items.push_back(str);
     string &s = m_items[m_items.size() - 1];
     if (s.size() >= len - 1)
	  s.resize(len - 1);
     updateCount();
     setCurIndex(count() - 1);
     refresh();
}

void ListView::removeItemAt(int i) {
     assert(i<count());
     m_items.erase(m_items.begin() + i);
     updateCount();
     if (i && m_curIndex == i)
	  setCurIndex(i - 1);
     refresh();
}

void ListView::setItemAt(int i, const std::string &str) {
     assert(i<count());
     m_items[i] = str;
}

int ListView::curIndex() const {
     return m_curIndex;
}

void ListView::setCurIndex(int i) {
     assert(i<count());
     if (i == m_curIndex)
	  return;
     if (m_editing)
	  editDone();
     m_curIndex = i;
     // emit onChange signal
     m_onChange(m_curIndex);
     refresh();
}

void ListView::setRect(Rect r) {
     m_rect = r;
     const int thick = ListView::LineThick;
     m_rowLabel.setStart(Point(m_rect.x() + thick, m_rect.y() + thick));
     m_rowLabel.setHeight(m_rect.height() - thick * 2);
     Rect labelRect = m_rowLabel.rect();
     int scrollW = m_scrollBar.scrollSkin()->thickness();
     m_scrollBar.setStart(Point(m_rect.x() + m_rect.width() - scrollW - thick, 
				m_rect.y() + thick));
     m_scrollBar.setLength(m_rect.height() - thick * 2);
     m_listRect.setX(labelRect.x() + labelRect.width() + thick);
     m_listRect.setY(m_rect.y() + thick);
     m_listRect.setWidth(m_rect.width() - scrollW - labelRect.width() - thick * 2);
     m_listRect.setHeight(m_rect.height() - thick * 2);
     m_scrollBar.setMaximum(count());
     m_scrollBar.setMinimum(wholeVisibleRows());
     m_scrollBar.setPos(m_offset + m_scrollBar.minimum());
}

void ListView::paint(bool all) {
     clear();
     al_draw_rectangle(m_rect.x(), m_rect.y(), m_rect.x() + m_rect.width(), 
		       m_rect.y() + m_rect.height(), m_pApp->skin()->color(Skin::FgColor), 
		       ListView::LineThick);
     m_rowLabel.paint();
     m_scrollBar.paint(all);
     paintList();
     m_needPaint = false;
}

void ListView::paintList() {
     Rect r = m_listRect;
     const int pad = ListView::Padding;
     const int thick = ListView::LineThick;
     r.setX(r.x() + pad);
     r.setHeight(m_pFont->height());
     int visRows = visibleRows();
     for (int i=0; i<visRows && i+m_offset < count(); i++) {
	  if (r.y() > m_listRect.y() + m_listRect.height())
	       break;
	  if (r.y() + r.height() > m_listRect.y() + m_listRect.height())
	       r.setHeight(m_listRect.y() + m_listRect.height() - r.y());
	  if (i == m_curIndex - m_offset) {
	       al_draw_filled_rectangle(r.x() - pad - thick, r.y(), 
					r.x() - pad - thick + r.width(), r.y() + r.height(),
					m_pApp->skin()->color(Skin::SelectedItemColor));
	       if (m_editing) {
		    int x = r.x() + m_pFont->width() * m_curCol;
		    int w = m_pFont->width();
		    al_draw_filled_rectangle(x, r.y(), 
					     x + w, r.y() + r.height(),
					     m_pApp->skin()->color(Skin::CursorColor));
	       }
	  }
	  m_pFont->print(m_items[i + m_offset], r);
	  r.setY(r.y() + m_pFont->height());
     }
}

int ListView::wholeVisibleRows() {
     return floor((float)m_listRect.height() / (float)m_pFont->height());
}

int ListView::visibleRows() {
     return ceil((float)m_listRect.height() / (float)m_pFont->height());
}

void ListView::incCurIndex() {
     int i = m_curIndex;
     if (i == NoIndex && count()) {
	  setCurIndex(0);
	  return;
     }
     i++;
     int visRows = wholeVisibleRows();
     if (i >= count() && count())
	  i = count() - 1;
     setCurIndex(i);
     if (m_curIndex - m_offset >= visRows)
	  setOffset(m_offset + 1);
     refresh();
}

void ListView::decCurIndex() {
     if (m_curIndex == NoIndex && count()) {
	  setCurIndex(count() - 1);
	  return;
     }
     setCurIndex(m_curIndex - 1);
     if (m_curIndex < 0 && count())
	  setCurIndex(0);
     if (m_curIndex - m_offset < 0)
	  setOffset(m_offset - 1);
     refresh();
}

void ListView::updateCount() {
     m_scrollBar.setMaximum(count());
     m_scrollBar.setMinimum(wholeVisibleRows());
     m_scrollBar.setPos(m_offset + m_scrollBar.minimum());
     m_rowLabel.setCount(count());
     m_rowLabel.setOffset(m_offset);
}

void ListView::setOffset(int offset) {
     m_offset = offset;
     m_rowLabel.setOffset(offset);
     m_scrollBar.setPos(m_scrollBar.minimum() + offset);
}

// scrollbar onPosChange slot
void ListView::updateOffset(int offset) {
     m_offset = offset - m_scrollBar.minimum();
     m_rowLabel.setOffset(m_offset);
}

void ListView::editName() {
     m_editing = true;
     m_grabKeys = true;
     m_curCol = 0;
     m_editIndex = m_curIndex;
     refresh();
}

void ListView::editDone() {
     m_editing = false;
     m_grabKeys = false;
     // emit onNameChange signal
     m_onNameChange(m_items[m_editIndex]);
     refresh();
}

void ListView::mouseDown(Point p, int button) {
     int i = m_curIndex;
     bool itemClicked = setIndexAtPoint(p);
     if (m_editing && (!itemClicked || itemClicked != i))
	  editDone();
     // current index clicked again - edit name
     else if (itemClicked && m_curIndex == i && !m_editing)
	  editName();
     // pass on event to child views
     View::mouseDown(p, button);
}

void ListView::keyDown(int keycode, int modifiers, int unichar) {
     unsigned int len = nameLength();
     string &str = m_items[m_curIndex];
     if (m_editing) {
	  if (keycode == ALLEGRO_KEY_ENTER) {
	       // editing done
	       editDone();
	  } else if (printable(unichar)) {
	       if (str.size()) {
		    string s = str.substr(m_curCol);
		    if (str.size() < (size_t)(m_curCol + 1))
			 str.resize(m_curCol + 1);
		    str[m_curCol] = unichar;
		    str += s;
		    if (str.size() >= len - 1)
			 str.resize(len - 1);
	       } else {
		    str.resize(1);
		    str[0] = unichar;
	       }
	       m_curCol++;
	       if ((unsigned int)m_curCol > len - 1)
		    m_curCol = len - 1;
	  } else if (keycode == ALLEGRO_KEY_BACKSPACE) {
	       if (m_curCol < 1)
		    return;
	       string s = str.substr(m_curCol);
	       str.resize(m_curCol - 1);
	       str += s;
	       m_curCol--;
	  } else if (keycode == ALLEGRO_KEY_DELETE) {
	       if ((unsigned int)m_curCol >= str.length())
		    return;
	       string s = str.substr(m_curCol + 1);
	       str.resize(m_curCol);
	       str += s;
	  } else if (keycode == ALLEGRO_KEY_LEFT) {
	       if (m_curCol < 1)
		    return;
	       m_curCol--;
	  } else if (keycode == ALLEGRO_KEY_RIGHT) {
	       if ((unsigned int)m_curCol >= str.length())
		    return;
	       m_curCol++;
	  }
     }
     refresh();
}

int ListView::nameLength() {
     return m_listRect.width() / m_pFont->width();
}

// returns whether an item was clicked or not
bool ListView::setIndexAtPoint(Point p) {
     if (!m_listRect.inside(p))
	  return false;
     int visRows = visibleRows();
     Rect r = m_listRect;
     r.setHeight(m_pFont->height());
     for (int i=0; i<visRows && i+m_offset < count(); i++) {
	  if (r.y() + r.height() > m_listRect.y() + m_listRect.height())
	       r.setHeight(m_listRect.y() + m_listRect.height() - r.y());
	  if (r.inside(p)) {
	       setCurIndex(i + m_offset);
	       refresh();
	       return true;
	  }
	  r.setY(r.y() + m_pFont->height());
     }
     return false;
}

signals2::connection ListView::onChange(const OnChange::slot_type &callback) {
     return m_onChange.connect(callback);
}

signals2::connection ListView::onNameChange(const OnNameChange::slot_type &callback) {
     return m_onNameChange.connect(callback);
}

void ListView::clearList() {
     m_items.clear();
     setOffset(0);
     m_rowLabel.setCount(count());
     m_scrollBar.setMaximum(count());
     m_scrollBar.setMinimum(wholeVisibleRows());
     m_scrollBar.setPos(m_offset + m_scrollBar.minimum());
     m_rowLabel.setCount(count());
     refresh();
}

void ListView::blur() {
     if (m_editing)
	  editDone();
}
